package com.basker.pisces.excel.poi;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.basker.pisces.excel.CellModel;
import com.basker.pisces.excel.CellStyleModel;
import com.basker.pisces.excel.IWorkBook;
import com.basker.pisces.excel.poi.utils.POIExcelUtils;

class POIWorkbook implements IWorkBook {

    private static final Logger LOGGER = LoggerFactory.getLogger(POIWorkbook.class);

    private Workbook workbook = null;

    private Map<String, CellStyle> styleCache = new HashMap<>();

    POIWorkbook(Workbook workbook) {
        this.workbook = workbook;
    }

    @Override
    public void addSheet(String sheetName) {
        Sheet sheet = this.workbook.getSheet(sheetName);

        if (sheet != null) {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("The workbook already contains a sheet named '" + sheetName + "'.");
            }

            return;
        }

        this.workbook.createSheet(sheetName);
    }

    @Override
    public Object getCellValue(int sheetIndex, int rowIndex, int columnIndex) {
        Sheet sheet = this.workbook.getSheetAt(sheetIndex);
        return getCellValue0(sheet, rowIndex, columnIndex);
    }

    @Override
    public Object getCellValue(String sheetName, int rowIndex, int columnIndex) {
        Sheet sheet = this.workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new IllegalArgumentException("Sheet Name:" + sheetName + " is not exist.");
        }

        return getCellValue0(sheet, rowIndex, columnIndex);
    }

    @Override
    public int getFirstRowNum(int sheetIndex) {
        Sheet sheet = this.workbook.getSheetAt(sheetIndex);
        return sheet.getFirstRowNum();
    }

    @Override
    public int getFirstRowNum(String sheetName) {
        Sheet sheet = this.workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new IllegalArgumentException("Sheet Name:" + sheetName + " is not exist.");
        }

        return sheet.getFirstRowNum();
    }

    @Override
    public int getLastRowNum(int sheetIndex) {
        Sheet sheet = this.workbook.getSheetAt(sheetIndex);
        return sheet.getLastRowNum();
    }

    @Override
    public int getLastRowNum(String sheetName) {
        Sheet sheet = this.workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new IllegalArgumentException("Sheet Name:" + sheetName + " is not exist.");
        }

        return sheet.getLastRowNum();
    }

    @Override
    public int getSheetCount() {
        return this.workbook.getNumberOfSheets();
    }

    @Override
    public String getSheetName(int sheetIndex) {
        Sheet sheet = this.workbook.getSheetAt(sheetIndex);
        if (sheet == null) {
            throw new IllegalArgumentException("Sheet index:" + sheetIndex + " is not exist.");
        }

        return sheet.getSheetName();
    }

    @Override
    public String[] getSheetNames() {
        int numberOfSheets = this.workbook.getNumberOfSheets();
        String[] names = new String[numberOfSheets];

        for (int i = 0; i < numberOfSheets; i++) {
            names[i] = this.workbook.getSheetName(i);
        }

        return names;
    }

    @Override
    public void save(File file) throws IOException {
        try (FileOutputStream os = new FileOutputStream(file)) {
            this.save(os);
        }
    }

    @Override
    public void save(OutputStream outputStream) throws IOException {
        this.workbook.write(outputStream);
    }

    @Override
    public void setCell(int sheetIndex, CellModel cell) {
        Sheet sheet = this.workbook.getSheetAt(sheetIndex);
        this.setCell0(sheet, cell);
    }

    @Override
    public void setCell(String sheetName, CellModel cell) {
        Sheet sheet = this.workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new IllegalArgumentException("Sheet Name:" + sheetName + " is not exist.");
        }

        this.setCell0(sheet, cell);
    }

    @Override
    public void setDefaultColumnStyle(int sheetIndex, int columnIndex, CellStyleModel styleModel) {
        Sheet sheet = this.workbook.getSheetAt(sheetIndex);
        CellStyle cellStyle = this.getCellStyle(styleModel);

        sheet.setDefaultColumnStyle(columnIndex, cellStyle);
    }

    @Override
    public void setDefaultColumnStyle(String sheetName, int columnIndex, CellStyleModel styleModel) {
        Sheet sheet = this.workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new IllegalArgumentException("Sheet Name:" + sheetName + " is not exist.");
        }
        CellStyle cellStyle = this.getCellStyle(styleModel);

        sheet.setDefaultColumnStyle(columnIndex, cellStyle);
    }

    private Cell getCell(Row row, int columnIndex, boolean nullThenCreate) {
        Cell cell = row.getCell(columnIndex);
        if (cell == null && nullThenCreate) {
            cell = row.createCell(columnIndex);
        }

        return cell;
    }

    private CellStyle getCellStyle(CellStyleModel styleModel) {
        // cellStyle是可复用的，无节制createCellStyle会造成内存隐患，另外cellStyle数量也有上线，xlsx为64000
        String key = styleModel.toString();
        CellStyle cellStyle = styleCache.get(key);

        if (cellStyle == null) {
            cellStyle = POIExcelUtils.createCellStyle(workbook, styleModel);
            styleCache.put(key, cellStyle);
        }

        return cellStyle;
    }

    private Object getCellValue0(Sheet sheet, int rowIndex, int columnIndex) {
        Row row = sheet.getRow(rowIndex);
        if (row == null) {
            return null;
        }

        Cell cell = row.getCell(columnIndex);
        if (cell == null) {
            return null;
        }

        return POIExcelUtils.getCellValue(cell);
    }

    private Row getRow(Sheet sheet, int rowIndex, boolean nullThenCreate) {
        Row row = sheet.getRow(rowIndex);
        if (row == null && nullThenCreate) {
            row = sheet.createRow(rowIndex);
        }

        return row;
    }

    private void setCell0(Sheet sheet, CellModel cell) {
        CellType poiCellType = POIExcelUtils.getPOICellType(cell);
        int rowIndex = cell.getRowIndex();
        int columnIndex = cell.getColumnIndex();

        Row row = getRow(sheet, rowIndex, true);
        Cell poiCell = this.getCell(row, columnIndex, true);

        poiCell.setCellType(poiCellType);
        POIExcelUtils.setCellValue(poiCell, cell.getValue());

        CellStyleModel styleModel = cell.getCellStyleModel();
        if (styleModel != null) {
            CellStyle cellStyle = this.getCellStyle(styleModel);
            poiCell.setCellStyle(cellStyle);
        }

    }

}
